<!DOCTYPE html>
<html>

<head>
  <title>canvas test-29 - 绘制虚线圆角矩形/虚线圆(弧)/虚线箭头</title>
  <style type="text/css">
  * {
    margin: 0;
    padding: 0
  }
  
  pre {
    padding: 10px 0
  }
  </style>
</head>

<body>
  <pre>
    绘制虚线圆角矩形/虚线圆(弧)/虚线箭头
  </pre>
  <p></p>
  <canvas id="stage" width="640" height="480" style="border: 1px solid red; margin: 20px"></canvas>
  <script src="raf.js"></script>
  <script src="vector2.js"></script>
  <script src="resource.js"></script>
  <script type="text/javascript">
  var canvas = document.getElementById('stage');
  var ctx = canvas.getContext('2d');

  var canvasWidth = canvas.width;
  var canvasHeight = canvas.height;
  var bbox = canvas.getBoundingClientRect();
  var PI2 = Math.PI * 2;
  var ARC = Math.PI / 180;
  var DEG = 180 / Math.PI;
  var centerX = canvasWidth / 2;
  var centerY = canvasHeight / 2;

  window.onload = rendering;

  function rendering(disableLight) {
    ctx.clearRect(0, 0, canvasWidth + 1, canvasHeight + 1);

    // dash arrow line
    drawDashArrowLine(20, 70, 100, 70);
    drawDashArrowLine(180, 70, 110, 70);
    drawDashArrowLine(230, 20, 230, 100);
    drawDashArrowLine(280, 100, 280, 20);
    drawDashArrowLine(300, 20, 400, 100);
    drawDashArrowLine(400, 20, 300, 100);
    drawDashArrowLine(420, 20, 450, 100);
    drawDashArrowLine(460, 100, 500, 20);
    drawDashArrowLine(550, 100, 510, 20);
    drawDashArrowLine(610, 20, 560, 100);

    // draw dash circle/arc
    drawDashArc(120, 220, 100, 0, 360);
    ctx.beginPath();
    ctx.arc(120, 220, 90, 0, PI2);
    ctx.stroke();
    drawDashArc(120, 220, 80, 0, 270, false, 10, 4);
    ctx.beginPath();
    ctx.arc(120, 220, 70, 0, Math.PI * 3 / 2);
    ctx.stroke();
    drawDashArc(120, 220, 60, 0, 270, true, 10, 5);
    ctx.beginPath();
    ctx.arc(120, 220, 50, PI2, Math.PI / 2, true);
    ctx.stroke();
    drawDashArc(120, 220, 40, 0, 90, false, 10, 6);
    drawDashArc(120, 220, 40, 90, 180, true, 10, 6);

    drawDashCircle(450, 220, 100);
    drawDashCircle(520, 220, 60);
    drawDashCircle(380, 220, 60);
    drawDashCircle(500 + 5, 220 - 5, 20);
    drawDashCircle(400 - 5, 220 - 5, 20);
    drawDashCircle(500 + 5, 220 - 5, 5);
    drawDashCircle(400 - 5, 220 - 5, 5);
    drawDashLine(450 - 10, 220, 450 + 10, 220);
    drawDashLine(450 - 20, 320 - 30, 450 + 20, 320 - 30);

    // draw round dash rect
    drawDashRoundRect(20, 350, 100, 100, 10);
    drawDashRoundRect(140, 350, 200, 100, 5);
  }

  function drawDashRoundRect(x, y, width, height, radius) {
    drawDashArc(x + radius, y + radius, radius, 90, 180, true); // top-left arc
    drawDashLine(x + radius, y, x + width - radius, y); // top line

    drawDashArc(x + width - radius, y + radius, radius, 0, 90, true); // top-right arc
    drawDashLine(x + width, y + radius, x + width, y + height - radius); // right line

    drawDashArc(x + width - radius, y + height - radius, radius, 0, 90); // bottom-right arc
    drawDashLine(x + width - radius, y + height, x + radius, y + height); // bottom line

    drawDashArc(x + radius, y + height - radius, radius, 90, 180); // bottom-left arc
    drawDashLine(x, y + height - radius, x, y + radius); // left line
  }

  function drawDashCircle(x, y, radius, clockwise, arcSize, arcGap) {
    drawDashArc(x, y, radius, 0, 360, clockwise, arcSize, arcGap);
  }

  function drawDashArrowLine(fromX, fromY, toX, toY, arrowSide, arrowDeg) {
    // apply default config
    arrowSide = arrowSide || 10;
    arrowDeg = arrowDeg || 50;

    var deltaX = toX - fromX;
    var deltaY = toY - fromY;
    var arc = Math.atan2(deltaY, deltaX);
    var thed = arrowDeg / 2 * ARC;
    var arc1 = arc - thed;
    var arc2 = arc + thed;
    var p1x = toX - arrowSide * Math.cos(arc1);
    var p1y = toY - arrowSide * Math.sin(arc1);
    var p2x = toX - arrowSide * Math.cos(arc2);
    var p2y = toY - arrowSide * Math.sin(arc2);

    drawDashLine(fromX, fromY, toX, toY);
    drawDashLine(p1x, p1y, toX, toY);
    drawDashLine(p2x, p2y, toX, toY);
  }

  // 绘制虚线圆弧
  function drawDashArc(x, y, radius, fromAngle, toAngle, clockwise, arcSize, arcGap) {
    var minSize = 8; // px
    var minGap = 2; // px;
    var minLen = (minSize + minGap) / radius * DEG; // 依据半径计算周长产生较为理想的间隙

    arcSize = (arcSize || minLen * 3 / 4) * ARC;
    arcGap = (arcGap || minLen / 4) * ARC;
    clockwise = !!clockwise; // default: false - anticlockwise

    var dir = clockwise ? -1 : 1;
    var fromArc = (clockwise ? 360 - fromAngle : fromAngle) * ARC;
    var toArc = (clockwise ? 360 - toAngle : toAngle) * ARC;
    var deltaArc = (toArc - fromArc) * dir;
    var arcStep = arcSize + arcGap;
    var rest = deltaArc % arcStep;
    var times = (deltaArc - rest) / arcStep;
    var curArc = fromArc;
    var fixup = 0;

    if (rest) {
      if (rest > arcGap) {
        fixup = rest - arcGap;
      } else {
        fixup = rest;
      }
    }

    if (clockwise) {
      fixup = -fixup;
      arcSize = -arcSize;
      arcStep = -arcStep;
    }

    ctx.beginPath();

    for (var i = 0; i < times; ++i) {
      var sx = x + radius * Math.cos(curArc);
      var sy = y + radius * Math.sin(curArc);
      ctx.moveTo(sx, sy);
      ctx.arc(x, y, radius, curArc, curArc + arcSize, clockwise);
      curArc += arcStep;
    }

    if (fixup) {
      var sx = x + radius * Math.cos(curArc);
      var sy = y + radius * Math.sin(curArc);
      ctx.moveTo(sx, sy);
      ctx.arc(x, y, radius, curArc, curArc + fixup, clockwise);
    }

    ctx.stroke();
  }

  // 绘制虚线段
  function drawDashLine(fromX, fromY, toX, toY, dashSize, dashGap) {
    // normalize arguments
    dashSize = dashSize || 5;
    dashGap = dashGap || 3;

    var deltaX = toX - fromX;
    var deltaY = toY - fromY;
    var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
    var cosa = deltaX / distance;
    var sina = deltaY / distance;
    var dashStep = dashSize + dashGap;
    var len = (distance / dashStep) << 0;
    var rest = distance % dashStep;
    var curX = fromX;
    var curY = fromY;
    var fixup = 0;
    var fixstart = true;
    var fixend = true;

    // ensure the line start and end with line instead of gap
    if (rest === 0) {
      fixup = dashSize / 2;
      len -= 1;
    } else if (rest > dashSize) {
      fixup = (rest - dashGap) / 2;
    } else {
      fixup = rest;
      fixstart = false;
    }

    ctx.beginPath();

    if (fixstart) {
      ctx.moveTo(curX, curY);
      curX += fixup * cosa;
      curY += fixup * sina;
      ctx.lineTo(curX, curY);

      curX += dashGap * cosa;
      curY += dashGap * sina;
    }

    for (var i = 0; i < len; ++i) {
      ctx.moveTo(curX, curY);
      curX += dashSize * cosa;
      curY += dashSize * sina;
      ctx.lineTo(curX, curY);

      curX += dashGap * cosa;
      curY += dashGap * sina;
    }

    if (fixend) {
      ctx.moveTo(curX, curY);
      curX += fixup * cosa;
      curY += fixup * sina;
      ctx.lineTo(curX, curY);
    }

    ctx.stroke();
  }
  </script>
</body>

</html>
